// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package ssa

// STREATE将值移动到更接近使用它们的块的位置。这可以减少所需的寄存器溢出量，如果它不能创建更多的活动值的话。
// 一个值可以移动到
// 支配其使用的所有块的任何块。
func tighten(f *Func) {
	canMove := make([]bool, f.NumValues())
	for _, b := range f.Blocks {
		for _, v := range b.Values {
			if v.Op.isLoweredGetClosurePtr() {
				// 必须留在入口区。
				continue
			}
			switch v.Op {
			case OpPhi, OpArg, OpArgIntReg, OpArgFloatReg, OpSelect0, OpSelect1, OpSelectN:
				// 钓鱼者需要留在他们的街区。
				// Arg必须留在入口块中。
				// 元组选择器必须与元组生成器一起使用。
				// SelectN最终通常是一个寄存器。
				continue
			}
			if v.MemoryArg() != nil {
				// 我们不能移动带有内存参数的值-它可能会使两个内存值跨越块边界。
				continue
			}
			// 计算需要寄存器的参数。
			narg := 0
			for _, a := range v.Args {
				if !a.rematerializeable() {
					narg++
				}
			}
			if narg >= 2 && !v.Type.IsFlags() {
				// 不要使用多个输入移动值，因为这可能会增加寄存器压力。
				// 我们对标志做了一个例外，因为我们希望标志生成器
				// 移到uses旁边（因为我们只有一个标志寄存器）。
				continue
			}
			canMove[v.ID] = true
		}
	}

	// 为快速最不常见祖先查询构建数据结构。
	lca := makeLCArange(f)

	// 对于每个可移动值，记录到目前为止主导所有用途的块。
	target := make([]*Block, f.NumValues())

	// 获取循环信息。
	// 我们使用这个来确保我们不会将一个值收紧到（更深的）循环中。
	idom := f.Idom()
	loops := f.loopnest()
	loops.calculateDepths()

	changed := true
	for changed {
		changed = false

		// 重置目标
		for i := range target {
			target[i] = nil
		}

		// 计算目标位置（仅适用于可移动值）。
		// 目标位置=支配树中所有使用中最不常见的祖先。
		for _, b := range f.Blocks {
			for _, v := range b.Values {
				for i, a := range v.Args {
					if !canMove[a.ID] {
						continue
					}
					use := b
					if v.Op == OpPhi {
						use = b.Preds[i].b
					}
					if target[a.ID] == nil {
						target[a.ID] = use
					} else {
						target[a.ID] = lca.find(target[a.ID], use)
					}
				}
			}
			for _, c := range b.ControlValues() {
				if !canMove[c.ID] {
					continue
				}
				if target[c.ID] == nil {
					target[c.ID] = b
				} else {
					target[c.ID] = lca.find(target[c.ID], b)
				}
			}
		}

		// 如果目标位置在循环内，
		// 将目标位置移动到循环头的正前方。
		for _, b := range f.Blocks {
			origloop := loops.b2l[b.ID]
			for _, v := range b.Values {
				t := target[v.ID]
				if t == nil {
					continue
				}
				targetloop := loops.b2l[t.ID]
				for targetloop != nil && (origloop == nil || targetloop.depth > origloop.depth) {
					t = idom[targetloop.header.ID]
					target[v.ID] = t
					targetloop = loops.b2l[t.ID]
				}
			}
		}

		// 将值移动到目标位置。
		for _, b := range f.Blocks {
			for i := 0; i < len(b.Values); i++ {
				v := b.Values[i]
				t := target[v.ID]
				if t == nil || t == b {
					// v不可移动，或已在正确位置。
					continue
				}
				// 将v移到主导其用途的区块。
				t.Values = append(t.Values, v)
				v.Block = t
				last := len(b.Values) - 1
				b.Values[i] = b.Values[last]
				b.Values[last] = nil
				b.Values = b.Values[:last]
				changed = true
				i--
			}
		}
	}
}

// phi使常数更接近phi用户。
// 这个过程避免了很多程序中有很多常量处于活动状态。
// 见第16407期。
func phiTighten(f *Func) {
	for _, b := range f.Blocks {
		for _, v := range b.Values {
			if v.Op != OpPhi {
				continue
			}
			for i, a := range v.Args {
				if !a.rematerializeable() {
					continue // 不是一个常数，我们可以移动
				}
				if a.Block == b.Preds[i].b {
					continue // 已经在正确的位置
				}
				// 复制一个，放在前一个块中。
				v.SetArg(i, a.copyInto(b.Preds[i].b))
			}
		}
	}
}
